// $Id: echo.c 65 2011-04-23 14:36:25Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <nio/buffer.h>
#include <nio/handler.h>

#include "log.h"
#include "headers.h"

#include "echo.h"

struct niohandler niohttp_echo_handler = {
		niohttp_echo_setup,
		niohttp_echo_teardown,
		niohttp_echo_handle_upstream,
		niohttp_echo_handle_downstream,
		niohttp_echo_free,
};

struct niohttp_echo *niohttp_echo_new(void)
{
	struct niohttp_echo *echo;

	niohttp_debug("constructing");

	if (!(echo = calloc(1, sizeof(*echo)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	return echo;

exception:
	niohttp_echo_free(echo);
	return NULL;
}

void niohttp_echo_free(void *obj)
{
	struct niohttp_echo *echo = obj;

	niohttp_debug("destructing: %p", echo);

	if (!echo) {
		return;
	}

	free(echo);
}

int niohttp_echo_setup(void *obj, const char *name)
{
	int rc;
	struct niohttp_echo *handler = obj;

	if (!handler || !name) {
		niohttp_error("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niohttp_debug("setting up: %s", name);

	handler->name = name;
	rc = 0;

finally:
	return rc;
}

int niohttp_echo_teardown(void *obj)
{
	int rc;
	struct niohttp_echo *handler = obj;

	if (!handler) {
		niohttp_error("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niohttp_debug("tearing down: %s", handler->name);

	if (handler->response) {
		niohttp_response_free(handler->response);
		handler->response = NULL;
	}

	handler->name = NULL;
	rc = 0;

finally:
	return rc;
}

int niohttp_echo_handle_upstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niohttp_request *req;
	struct niobuffer *content;
	struct niohttp_header_list *headers;
	struct niohttp_header *header;
	struct niohttp_echo *handler = obj;
	char temp[11];

	if (!handler || !message || !channel) {
		niohttp_error("null pointer");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("handling upstream: %s", handler->name);

	req = message;
	headers = niohttp_request_headers(req);
	content = niochannel_buffer(channel);

	niobuffer_clear(content);

	if ((rc = niobuffer_putf(content, "<html><head>"
			"<title>Echo</title></head>"
			"<body>"))) {
		niohttp_error("failed to putf: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niobuffer_putf(content, "<h1>Request</h1>"
			"<ul><li>Method: %s</li>"
			"<li>URI: %s</li><li>Protocol: %s</li>"
			"<li>Version: %s</li></ul>"
			"<h1>Request Headers</h1>"
			"<ul>",
			niohttp_request_method(req),
			niohttp_request_uri(req),
			niohttp_request_protocol(req),
			niohttp_request_version(req)))) {
		niohttp_error("failed to putf: %s", nio_strerror(rc));
		goto finally;
	}

	TAILQ_FOREACH(header, headers, next) {
		if ((rc = niobuffer_putf(content, "<li>%s: %s</li>",
				header->name, header->value))) {
			niohttp_error("failed to putf: %s", nio_strerror(rc));
			goto finally;
		}
	}

	if ((rc = niobuffer_putf(content, "</ul></body></html>"))) {
		niohttp_error("failed to putf: %s", nio_strerror(rc));
		goto finally;
	}

	niobuffer_flip(content);

	if (!(handler->response = niohttp_request_create_response(req,
			200, "OK"))) {
		niohttp_error("failed to create response");
		rc = NIOHTTP_ENOMEM;
		goto finally;
	}

	if ((rc = niohttp_response_set_header(handler->response,
			"Content-Type", "text/html"))) {
		niohttp_error("failed to set content-type: %s",
				niohttp_strerror(rc));
		goto finally;
	}

	snprintf(temp, sizeof(temp), "%d", niobuffer_remaining(content));

	if ((rc = niohttp_response_set_header(handler->response,
			"Content-Length", temp))) {
		fprintf(stdout, "failed to set content-length: "
				"%s\n", niohttp_strerror(rc));
		goto finally;
	}

	if ((rc = niochannel_send_downstream(channel, handler->name,
			handler->response))) {
		niohttp_error("failed to send downstream: %s",
				nio_strerror(rc));
		goto finally;
	}

finally:
	if (rc != 0) {
		if (handler->response) {
			niohttp_response_free(handler->response);
			handler->response = NULL;
		}
	}
	return rc;
}

int niohttp_echo_handle_downstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niohttp_echo *handler = obj;

	if (!handler) {
		niohttp_error("null pointer");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("handling downstream: %s", handler->name);

	if ((rc = niochannel_send_downstream(channel, handler->name,
			message))) {
		niohttp_error("failed to send downstream: %s",
				nio_strerror(rc));
		goto finally;
	}

finally:
	return rc;
}
